#include "s19file.h"
#include "system_path.h"
#include "file_utils.h"
#include "bit_utils.h"

#include <fstream>
#include <iostream>
#include <vector>
#include <QDebug>


namespace fs = std::filesystem;


int StringToDecNum(const std::string& s)
{
    int num = 0;
    for (auto & c : s) {
        if (c >= '0' && c <= '9') {
            num = num * 10 + static_cast<int>(c - '0');
        }
        else {
            break;
        }
    }
    return num;
}

int StringToHexNum(const std::string &s)
{
    int num = 0;
    std::string numstr;
    if (s.empty()) return 0;

    /*
     * Ignore 0x, 0X
     */
    if (s[0] == '0' && std::tolower(s[1]) == 'x') {
        numstr = s.substr(2);
    }
    else {
        numstr = s;
    }

    for (auto & c : numstr) {
        if (c >= '0' && c <= '9') {
            num = num * 16 + static_cast<int>(c - '0');
        }
        else if ((c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) {
            num = num * 16 + static_cast<int>(std::tolower(c) - 'a' + 10);
        }
        else {
            break;
        }
    }
    return num;
}

/**
 * @brief 计算校验和
 * checksum = 0xFF - (count + address + data)
 *
 * @note 计算校验和时, 必须按字节求和
 *
 * @param r
 * @return
 */
static uint8_t CalculateCheckSum(const Record & r)
{
#if 1
    int sum = 0;
    uint8_t addressarr[4];

    sum += r.count;

    convert_u32_to_u8array(r.address, addressarr);
    for (int i = 0; i < sizeof(addressarr) / sizeof(addressarr[0]); i++) {
        sum += addressarr[i];
    }

    for (auto &v : r.data) {
        sum += v;
    }

    uint8_t checksum = 0xFF - (sum & 0xFF); // or 0xFF - sum
    return checksum;
#else
    int sum = 0;
    sum += r.count;
    sum += r.address;
    for (auto &v : r.data) {
        sum += v;
    }

    uint8_t checksum = ~sum + 1;
    return checksum;
#endif
}


std::string default_path()
{
    return current_project_path();
}

S19File::S19File(const std::string &filepath)
    : m_filePath(filepath), m_errorCode(0), m_errorString("")
{
}

bool S19File::LoadFile()
{
    if (!CheckFile()) return false;

    /*
     * Check if can open the file
     */
    std::ifstream file(m_filePath, std::ios::ate | std::ios::in);
    if (!file.is_open()) {
        m_errorString = "Error: Failed to open file " + m_filePath;
        return false;
    }

    /*
     * Check if can get the file size.
     */
    auto file_size = file.tellg();
    if (std::ifstream::pos_type(-1) == file_size) {
        m_errorString = "Error: Failed to get file size of " + m_filePath;
        return false;
    }

    /*
     * Parse the file line by line
     */
    int row = 0;
    char buf[256];
    file.seekg(0, std::fstream::beg);
    while (!file.eof()) {
        file.getline(buf, sizeof(buf));
        row++;

        if (!ParseLine(buf, row)) {
            return false;
        }
    }

    return true;
}

bool S19File::ParseLine(const std::string line, int linenum)
{
    Record record;
    std::string countstr;
    std::string addressstr;
    std::string checksumstr;
    int i = 0;
    int addresscharnum = 4;
    constexpr int kLineCharNumMin = (1 + 2 + 1) * 2; // 1byte for type, 2byte for address, 1byte for checksum

    if (line.empty()) {
        return true;
    }
    else if (line.size() < kLineCharNumMin) {
        m_errorString = "Error: Illegal length at line " + std::to_string(linenum) + " : " + line;
        return false;
    }

    if (line[0] != 'S' && line[0] != 's') {
        m_errorString = "Error: Not start with 'S' at line " + std::to_string(linenum) + " : " + line;
        return false;
    }

    /*
     * Parse line by type segment. Notes: 1byte = 2char (ASCII)
     */
    switch(line[1]) {
    case '0': // ignore
        break;

    case '1': // no break
    case '2': // no break
    case '3':
    {
        record.type = static_cast<RecordType>(line[1] - '0');
        addresscharnum = (line[1] - '0' + 1) * 2;
        int toreadcharnum= 0;
        int hexnum = 0;

        /*
         * Read count segment, 1byte = 2char
         */
        i = 2;
        countstr += line[i++];
        countstr += line[i++];
        hexnum = StringToHexNum(countstr);
        record.count = hexnum;
        toreadcharnum = hexnum * 2;
        if (toreadcharnum > line.size() - 2 * 2) { // -2byte: 1 for type, 1 for count
            m_errorString = "Error: Invalid count segment(count = " + std::to_string(toreadcharnum)
                            + ") at line " + std::to_string(linenum) + " : " + line;
            return false;
        }

        /*
         * Read address segment, 2/3/4byte = 4/6/8char
         */
        while (addresscharnum--) {
            addressstr += line[i++];
            toreadcharnum--;
        }
        record.address = StringToHexNum(addressstr); // multiple bytes

        /*
         * Read data segment, 0-64byte
         */
        while (i < line.size() - 1) {
            if (toreadcharnum <= 2) { // 1byte left for checksum
                break;
            }
            toreadcharnum -= 2;

            std::string datastr;
            datastr += line[i++];
            datastr += line[i++];
            hexnum = StringToHexNum(datastr);
            record.data.push_back(hexnum);
        }

        /*
         * Read checksum segment, 1byte
         */
        checksumstr += line[i++];
        checksumstr += line[i++];
        hexnum = StringToHexNum(checksumstr);
        record.checksum = hexnum;

        if (record.checksum != CalculateCheckSum(record)) {
            m_errorString = "Error: invalid checksum at line " + std::to_string(linenum) +  ": " + line;
            return false;
        }

        m_records.push_back(record);
    }
    break;

    case '4': // reserved
        break;

    case '5': // ignore
        break;

    case '6': // ignore
        break;

    case '7': // no break
    case '8': // no break
    case '9':
    {
        record.type = static_cast<RecordType>(line[1] - '0');
        addresscharnum = (4 - (line[1] - '7')) * 2; // "S7": 4; "S8": 3; "S9": 2
        int toreadcharnum= 0;
        int hexnum = 0;

        /*
         * Read count segment, 1byte = 2char
         */
        i = 2;
        countstr = line[i++];
        countstr = line[i++];
        hexnum = StringToHexNum(countstr);
        record.count = hexnum;
        toreadcharnum = hexnum * 2;
        if (toreadcharnum > line.size() - 2 * 2) { // 2byte left: 1 for type, 1 for count
            m_errorString = "Error: Invalid count segment(count = " + std::to_string(toreadcharnum)
                            + " at line: " + std::to_string(linenum) +  ": " + line;
            return false;
        }

        /*
         * Read address segment, 2/3/4byte = 4/6/8char
         */
        while (addresscharnum--) {
            addressstr += line[i++];
            toreadcharnum--;
        }
        record.address = StringToHexNum(addressstr); // multiple bytes

        /*
         * No data segment
         */

        /*
         * Read checksum segment, 1byte = 2char
         */
        checksumstr += line[i++];
        checksumstr += line[i++];
        hexnum = StringToHexNum(checksumstr);
        record.checksum = hexnum;

        if (record.checksum != CalculateCheckSum(record)) {
            m_errorString = "Error: Invalid checksum at line " + std::to_string(linenum) +  ": " + line;
            return false;
        }

        m_records.push_back(record);
    }
    break;

    default: // unknown type number
        m_errorString = "Error: Unknown type number at line " + std::to_string(linenum) +  ": " + line;
        return false;
    }

    return true;
}

bool S19File::CheckFile() const
{
    if (!FileUtils::IsFile(m_filePath)) {
        m_errorString = "Error: Not a regular file.";
        return false;
    }
    if (!FileUtils::IsExist(m_filePath)) {
        m_errorString = "Error: File does not exist.";
        return false;
    }
    return true;
}

void S19File::PrintRecords() const
{
    std::cout << "Total number of Record: " << m_records.size() << std::endl;
    for (int i = 0, row = 1; i < m_records.size(); i++, row++) {
        const Record &r = m_records[i];
        std::cout << std::dec;
        std::cout << row << ": S" << static_cast<int>(r.type);
        std::cout << std::hex;
        std::cout << " " << static_cast<int>(r.count)
                  << " " << std::setw(8) << std::setfill('0') << r.address
                  << " {";

        for (const auto & d : r.data) {
            std::cout << std::setw(2) << std::setfill('0') << static_cast<int>(d) << " ";
        }
        std::cout << "} " << std::setw(2) << std::setfill('0') << static_cast<int>(r.checksum) << std::endl;
    }
}
